package org.jsbd.boss.task;

import com.alibaba.fastjson.JSONObject;
import org.apache.commons.codec.language.Soundex;
import org.apache.commons.lang.StringUtils;
import org.jsbd.boss.common.redis.RedisClient;
import org.jsbd.boss.dao.IBrushApkConfigDao;
import org.jsbd.boss.dao.IDmChannelRetentDAO;
import org.jsbd.boss.domian.channel.BrushApkConfig;
import org.jsbd.boss.domian.channel.RetentRecordVo;
import org.jsbd.boss.domian.channel.RetentTaskVo;
import org.jsbd.boss.service.IApkSimulatorService;
import org.jsbd.boss.service.IChannelLimitService;
import org.jsbd.boss.service.LogManager;
import org.jsbd.boss.service.SimulatorManager;
import org.jsbd.boss.service.impl.CacheParamManager;
import org.jsbd.boss.service.impl.TaskLuaManager;
import org.jsbd.boss.util.CacheKey;
import org.jsbd.boss.util.DateUtil;
import org.jsbd.boss.util.RandomUtils;
import org.jsbd.boss.util.SpringContextUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.connection.ReturnType;

import java.util.ArrayList;
import java.util.List;

/**
 * 生成可用留存任务
 *
 * @Date 2018/9/17 14:23
 * @Author xupengcheng [xupengcheng@zhexinit.com]
 */
public class RetentQueueTask implements Runnable {

    public static final Logger logger = LoggerFactory.getLogger(RetentQueueTask.class);

    private List<RetentRecordVo> recordList;

    private IApkSimulatorService apkSimulatorService;

    private SimulatorManager simulatorManager;

    private TaskLuaManager taskLuaManager;

    private RedisClient redisClient;

    private RedisClient redisClientTemp;

    private CacheParamManager cacheParamManager;

    private IDmChannelRetentDAO channelRetentDAO;

    private IChannelLimitService channelLimitService;

    private IBrushApkConfigDao brushApkConfigDao;

    private LogManager logManager;

    private long daysOfTime;

    public RetentQueueTask(List<RetentRecordVo> recordList) {
        this.recordList = recordList;
        apkSimulatorService = SpringContextUtils.getBean(IApkSimulatorService.class);
        simulatorManager = SpringContextUtils.getBean(SimulatorManager.class);
        taskLuaManager = SpringContextUtils.getBean(TaskLuaManager.class);
        redisClient = SpringContextUtils.getBean("redisClient");
        redisClientTemp = SpringContextUtils.getBean("redisClientTemp");
        cacheParamManager = SpringContextUtils.getBean(CacheParamManager.class);
        channelRetentDAO = SpringContextUtils.getBean(IDmChannelRetentDAO.class);
        brushApkConfigDao = SpringContextUtils.getBean(IBrushApkConfigDao.class);
        channelLimitService = SpringContextUtils.getBean(IChannelLimitService.class);
        logManager = SpringContextUtils.getBean(LogManager.class);
    }


    private long getRetentLockTime() {
        long expireTime = 30;
        Object apkTimeStr = cacheParamManager.getParamValue(CacheKey.RETENT_TASK_DATA_LOCK);
        if (apkTimeStr != null) {
            expireTime = Long.parseLong(apkTimeStr.toString());
        }
        return expireTime;
    }

    @Override
    public void run() {
        daysOfTime = DateUtil.getDaysOfTime();
        if (recordList == null || recordList.isEmpty()) {
            return;
        }
        String apkQueueLock = CacheKey.R_T_Q_L_KEY + recordList.get(0).getApkId();
        if (redisClient.evalSetNx(apkQueueLock, getRetentLockTime())) {
            long start = System.currentTimeMillis();
            for (RetentRecordVo recordVo : recordList) {
                try {
                    updateRetentQueue(recordVo);
                } catch (Exception e) {
                    logger.error("留存更新报错 {} {}", recordVo.getMark(), recordVo.getDayOrMonth());
                }
            }
            logManager.printLog(logger, "留存更新时间 {} 天 {} 耗时 {}", new Object[]{recordList.get(0).getMark(), daysOfTime, System.currentTimeMillis() - start});
            redisClient.remove(apkQueueLock);
        }
    }

    private void updateRetentQueue(RetentRecordVo recordVo) {
        String apk = recordVo.getMark();
        String sysVer = apkSimulatorService.getVer(apk);
        Integer dayOrMonth = recordVo.getDayOrMonth();
        Integer taskType = recordVo.getTaskType();

        if (taskType == null || taskType != 1) {
            return;
        }

        int maxSimulators = simulatorManager.getVerCanUseSimulator(sysVer);
        if (maxSimulators <= 0) {
            return;
        }
        Integer[] apkTaskQueueSize = taskLuaManager.getQueueSize(new String[]{CacheKey.VER_TASK_QUEUE + sysVer});
        int hasQueueSize = apkTaskQueueSize[0];
        int leftSize = maxSimulators - hasQueueSize;
        if (leftSize > 0) {
            Integer apkId = recordVo.getApkId();
            Integer execTimes = recordVo.getExecTimes();
            int length = taskLuaManager.getLeftNumOfDayTimes(apk, dayOrMonth, execTimes, daysOfTime);
            if (length > leftSize) {
                length = leftSize;
            }
            logManager.printLog(logger, "apk {} length {} queueSize {} maxSimulators {} exectimes {} sysver {} dayOrMonth {}", new Object[]{apk, length, hasQueueSize, maxSimulators, execTimes, sysVer, dayOrMonth});
            if (length > 0) {
                String taskQueueKey = CacheKey.R_T_Q_D_KEY2 + daysOfTime + apkId + recordVo.getTaskType() + dayOrMonth + execTimes;
                List<String> retentkeyAndArgs = new ArrayList<>();
                retentkeyAndArgs.add(CacheKey.AR_TASK2 + daysOfTime + apk);
                retentkeyAndArgs.add(dayOrMonth + "exectotal");
                int queueSize = 0;
                retentkeyAndArgs.add(CacheKey.VER_TASK_QUEUE + sysVer);
                retentkeyAndArgs.add(String.valueOf(DateUtil.leftTimeToMidNight() / 1000));
                BrushApkConfig[] brushConfigs = getBrushConfig(apkId);
                while (length > 0) {
                    String task = redisClientTemp.evalRpop(taskQueueKey);
                    if (task == null) {
                        break;
                    }
                    //修改最后执行时间
                    JSONObject retentTaskVo = JSONObject.parseObject(task);
                    String day = retentTaskVo.getString("currentDate");
                    String imsi = retentTaskVo.getString("imsi");
                    updateRetentRecord(apk, imsi, day.toString(), DateUtil.getCurrentDateFormatStr("yyyy-MM-dd HH:mm:ss"));
                    if (taskType == 1 && execTimes == 1) {
                        task = initBrushTaskVo(brushConfigs, retentTaskVo, task, apk, apkId);
                    }
                    queueSize++;
                    retentkeyAndArgs.add(task);
                    length--;
                }
                retentkeyAndArgs.add(2, queueSize + "");
                if (retentkeyAndArgs.size() > 5) {
                    logManager.printLog(logger, "apk {} lpush task size {} dayOrMonth {} exectimes {}", new Object[]{apk, queueSize, dayOrMonth, execTimes});
                    String apkTimesRecord = CacheKey.AR_DAY_HASH_KEY + daysOfTime + apk;
                    String fieldKey = dayOrMonth + ":" + execTimes + ":exectotal";
                    redisClient.evalHincrby(apkTimesRecord, fieldKey, queueSize);
                    redisClient.eval(getRetentMarkQueueLua(), ReturnType.BOOLEAN, retentkeyAndArgs.toArray(new String[0]));
                }
            }
        }
    }


    /**
     * getRetentMarkQueueLua:留存任务添加到队列之后 修改任务执行数
     *
     * @return
     * @author <a href="mailto:xupengcheng@zhexinit.com" >徐鹏程</a>
     */
    private String getRetentMarkQueueLua() {
        return "redis.call('hincrby',KEYS[1],KEYS[2],KEYS[3]) redis.call('hincrby',KEYS[1],'exectotal',KEYS[3]) redis.call('expire',KEYS[1],KEYS[5]) for i = 6,#(KEYS) do redis.call('lpush',KEYS[4], KEYS[i]) end return true";
    }


    private String initBrushTaskVo(BrushApkConfig[] brushApkConfigs, JSONObject retentTaskVo, String task, String apk, Integer apkId) {
        if (brushApkConfigs != null && brushApkConfigs.length > 0) {
            try {
                for (BrushApkConfig brushConfig : brushApkConfigs) {
                    Integer minFilterDay = brushConfig.getMinFilterDay() == null ? 0 : brushConfig.getMinFilterDay();
                    Integer maxFilterDay = brushConfig.getMaxFilterDay() == null ? 0 : brushConfig.getMaxFilterDay();
                    if (minFilterDay > maxFilterDay) {
                        int temp = minFilterDay;
                        minFilterDay = maxFilterDay;
                        maxFilterDay = temp;
                    }
                    String day = retentTaskVo.getString("currentDate");
                    String imsi = retentTaskVo.getString("imsi");
                    String lastExecDay = null;
                    Integer dayLimit = brushConfig.getDayLimit();
                    Integer brushId = brushConfig.getBrushId();
                    Integer brushTimeFlag = brushConfig.getBrushTimeFlag();
                    if (brushTimeFlag != null && "1".equals(brushTimeFlag.toString())) {
                        lastExecDay = retentTaskVo.getString("brushTime");
                    }
                    if (StringUtils.isBlank(lastExecDay)) {
                        lastExecDay = retentTaskVo.getString("lastExecDate");
                    }
                    String currentDay = DateUtil.getCurrentDateStr();
                    if (StringUtils.isBlank(lastExecDay)) {
                        lastExecDay = currentDay;
                    } else {
                        lastExecDay = lastExecDay.replace("-", "").substring(0, 8);
                    }
                    int preDay = DateUtil.getPreDay(lastExecDay);
                    String dayReordKey = CacheKey.AR_BRUSH_TASK + daysOfTime + apk;
                    logManager.printLog(logger, "执行记录 apk {} day {} brushTotal {} brushexectotal {}", new Object[]{apk, day, redisClient.evalHget(dayReordKey, day + ":" + "brushtotal"), redisClient.evalHget(dayReordKey, day + ":" + "brushexectotal")});
                    if (minFilterDay == 0 || (preDay - 1 >= minFilterDay && preDay - 1 <= maxFilterDay)) {
                        Long result = (Long) redisClient.eval(checkBrushLua(), ReturnType.VALUE, new String[]{
                                CacheKey.BRUSH_RECORD,
                                currentDay + brushId + "execTotal",
                                dayLimit.toString(),
                                CacheKey.BRUSH_RECORD + currentDay + brushId,
                                brushConfig.getCode() + "-" + apkId + "exectotal",
                                dayReordKey,
                                day + ":" + "brushtotal",
                                day + ":" + "brushexectotal"
                        });
                        if (result.intValue() == 1) {
                            retentTaskVo.put("brushUrl", brushConfig.getUrl());
                            retentTaskVo.put("brushId", brushId);
                            retentTaskVo.put("brushCode", brushConfig.getCode());
                            retentTaskVo.put("brushName", brushConfig.getBrushTitle());
                            retentTaskVo.put("brushDay", currentDay);

                            String expireTime = (String) cacheParamManager.getParamValue("BRUSH_IMSI_RECORD_TIME");
                            if (expireTime == null) {
                                expireTime = "86400";
                            }
                            redisClient.put(CacheKey.BRUSHID_RECORD_KEY + currentDay + imsi + apkId, brushId + "-" + brushConfig.getCode(), new Long(expireTime));
                            logManager.printLog(logger, "{} {} 拉活条件满足 brushId {} 日限 {} {} {} 最后执行天 {} {} {} ", new Object[]{apk, imsi, brushId, dayLimit, minFilterDay, preDay, lastExecDay, result.intValue(), brushConfig.getCode()});
                            return JSONObject.toJSONString(retentTaskVo);
                        } else {
                            logManager.printLog(logger, "{} {} 拉活条件不满足 brushId {} 日限 {} 超量了 {} 最后执行天 {} {} result {} ", new Object[]{apk, imsi, brushId, dayLimit, minFilterDay, preDay, lastExecDay, result.intValue()});
                        }
                    } else {
                        logManager.printLog(logger, "{} {} 拉活条件不满足 天限制 brushId {} 日限 {} {} 最后执行天 {} {} ", new Object[]{apk, imsi, brushId, dayLimit, minFilterDay, preDay, lastExecDay});
                    }
                }
            } catch (Exception e) {
                logger.error("拉活异常 {} {}", apk, e);
                return task;
            }
        }
        return task;
    }

    /**
     * updateRetentRecord:更新留存执行记录
     *
     * @param apk
     * @param imsi
     * @param day
     * @author <a href="mailto:xupengcheng@zhexinit.com" >徐鹏程</a>
     */
    private void updateRetentRecord(String apk, String imsi, String day, String lastExecDate) {
        RetentTaskVo taskLog = new RetentTaskVo();
        taskLog.setImsi(imsi);
        taskLog.setMark(apk);
        taskLog.setCurrentDate(day);
        taskLog.setLastExeDate(lastExecDate);
        channelRetentDAO.updateRetentTimes(taskLog);
    }


    private String checkBrushLua() {
        StringBuilder sb = new StringBuilder();
        sb.append(" local execTotal=redis.call('hget', KEYS[1], KEYS[2]) ");
        sb.append(" local brushTotal=redis.call('hget', KEYS[6], KEYS[7]) ");
        sb.append(" local brushExecTotal=redis.call('hget', KEYS[6], KEYS[8]) ");
        sb.append(" if execTotal then else execTotal=0 end");
        sb.append(" if brushTotal then else brushTotal=0 end");
        sb.append(" if brushExecTotal then else brushExecTotal=0 end");
        sb.append(" if tonumber(execTotal)>=tonumber(KEYS[3]) then return 3 end");
        sb.append(" if tonumber(brushExecTotal)>=tonumber(brushTotal) then return 2 end");
        sb.append(" redis.call('hincrby', KEYS[1], KEYS[2], 1)");
        sb.append(" redis.call('hincrby', KEYS[4], KEYS[5], 1)");
        sb.append(" redis.call('hincrby', KEYS[6], KEYS[8], 1)");
        sb.append(" return 1");
        return sb.toString();
    }

    private BrushApkConfig[] getBrushConfig(Integer apkId) {
        BrushApkConfig brushConfig = new BrushApkConfig();
        brushConfig.setApkId(apkId);
        brushConfig.setIsBrush(1);
        List<BrushApkConfig> brushList = brushApkConfigDao.selectList(brushConfig);
        if (brushList != null && !brushList.isEmpty()) {
            for (BrushApkConfig config : brushList) {
                if (config.getIsLimit() != null && config.getIsLimit() == 1) {
                    int timeLimit = channelLimitService.getTimeLimit(config.getBrushId().toString(), config.getDayLimit());
                    config.setDayLimit(timeLimit);
                }
            }
            return (BrushApkConfig[]) RandomUtils.randomObjs(brushList.toArray(new BrushApkConfig[0]));
        }
        return null;
    }

}
